package dts_tcp

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"github.com/zing-dev/go-bit-bytes/bit"
	"io"
	"math"
	"time"
)

const (
	LightSpeed  = 100
	K           = 1000
	Microsecond = 1
	Second      = Microsecond * K * K
)

type Protocol struct {
	client       *Client
	Config       *Config
	DeviceCode   string // 硬件设备码
	DeviceNumber byte   // 设备编号
}

func NewProtocol(client *Client, config *Config) *Protocol {
	return &Protocol{
		client: client,
		Config: config,
	}
}

func (p *Protocol) Read() {
	var (
		header = make([]byte, HeaderLength)
		// 获取当前包的总大小
		total uint16
		// 余下数据包括包尾
		data []byte

		conn = p.client.conn
	)

	defer func() {
		err := conn.Close()
		if err != nil {
			PushLog(E, fmt.Sprintf("关闭DTS客户端<%s>失败: %s", conn.RemoteAddr(), err))
			return
		}
		if p.DeviceCode != "" {
			p.client.setStatus(Disconnected)
		}
	}()

	PushLog(I, DataFrameQueryCodeStart.String())
	_, err := conn.Write(QUECmdHID())
	if err != nil {
		PushLog(E, fmt.Sprintf("读取硬件设备码失败: %s", err))
		return
	}
	time.AfterFunc(time.Second, func() {
		PushLog(I, "发送复位命令")
		_, err = conn.Write(MRSCmdFW())
		if err != nil {
			PushLog(E, fmt.Sprintf("发送复位命令失败: %s", err))
			return
		}
	})
	bit.DefaultEndPont = binary.LittleEndian
	for {
		n, err := conn.Read(header[:1])
		if err != nil {
			PushLog(E, fmt.Sprintf("读取头指针信息失败: %s", err))
			return
		}
		if n == 0 {
			return
		}
		if n != len(header[:1]) {
			continue
		}
		var exist = false
		for _, frame := range DataFrameStart {
			if frame == DataFrame(header[0]) {
				exist = true
				break
			}
		}
		if !exist {
			continue
		}
		n, err = io.ReadFull(conn, header[1:8])
		if err != nil {
			PushLog(E, fmt.Sprintf("读取头信息失败: %s", err))
			return
		}
		if n != len(header[1:8]) {
			PushLog(E, "读取头信息长度非法")
			return
		}

		if !bytes.Equal(DataFrameFlag, header[1:8]) {
			PushLog(E, "读取头信息标志位非法")
			return
		}

		n, err = io.ReadFull(conn, header[8:])
		if err != nil {
			PushLog(E, fmt.Sprintf("读取头信息设备id和包长失败: %s", err))
			return
		}
		if n != len(header[8:]) {
			PushLog(E, "读取头信息设备id和包长长度非法")
			continue
		}
		p.DeviceNumber = header[8]
		total = bit.ToUint16(header[9:11])
		data = make([]byte, total-HeaderLength)
		n, err = io.ReadFull(conn, data)
		if err != nil {
			PushLog(E, fmt.Sprintf("读取数据信息失败: %s", err))
			return
		}
		if n != len(data) {
			PushLog(E, "读取数据信息长度非法")
			return
		}
		if DataFrame(data[len(data)-1]) != DataFrameEnd {
			PushLog(E, fmt.Sprintf("非法帧尾数据: %x", data[len(data)-1]))
			continue
		}
		dataFrame := DataFrame(header[0])
		switch dataFrame {
		case DataFrameQueryRLStart:
		case DataFrameQueryALStart:
		case DataFrameQueryCAStart:
			p.decodeChannelCalculationParams(data)
		case DataFrameQueryCHStart:
		case DataFrameQueryCodeStart:
			p.decodeDeviceCode(data)
		case DataFrameQueryCMStart:
			p.decodeCommonParams(data)
		case DataFrameDeviceParamsStart:
		case DataFrameTemperatureStart:
			flag := DataFlag(data[0])
			if p.Config.FilterSignal && flag != FlagTemperature {
				// 过滤温度斯托克和反斯托克信号数据
				// FlagStokes
				// FlagAntiStokes
				continue
			}
			p.decodeTemperature(data)
			continue
		case DataFrameFiberBrokenAlarmStart:
			p.decodeBrokenAlarm(data)
		case DataFrameConstantTemperatureAlarmStart, DataFrameDifferentialTemperatureAlarmStart:
			p.decodeTemperatureAlarms(dataFrame, data)
		case DataFrameYesStart:
		case DataFrameNoStart:
		default:
		}
		PushLog(W, dataFrame.String())
	}
}

// 解码断纤报警
func (p *Protocol) decodeBrokenAlarm(data []byte) {
	var (
		channelNumber = data[0] + 1
		position      = float32(bit.ToUint16(data[1:3])) * p.Config.Precision
		info          = DeviceInfo{
			DeviceCode:    p.DeviceCode,
			DeviceNumber:  p.DeviceNumber,
			ChannelNumber: channelNumber,
			Address:       p.client.conn.RemoteAddr().String(),
		}
	)
	fiberBroken := ChannelBrokenAlarm{Info: info, Position: position, At: time.Now()}
	LoadCallsFilterCodeRun(CodeFiberBrokenAlarm, fiberBroken)
	PushLog(W, fmt.Sprintf("测温主机<%s>设备码<%s>通道<%d>在<%f>米发生断纤报警", info.Address, p.DeviceCode, channelNumber, fiberBroken.Position))
}

// 报警数据
func (p *Protocol) decodeTemperatureAlarms(dataFrame DataFrame, data []byte) {
	var (
		count         = data[0] // 通道报警总计数
		channelNumber = data[1] + 1
		alarms        = make([]Alarm, count)
		info          = DeviceInfo{
			DeviceCode:    p.DeviceCode,
			DeviceNumber:  p.DeviceNumber,
			ChannelNumber: channelNumber,
			Address:       p.client.conn.RemoteAddr().String(),
		}
	)
	content := ""
	for i := range alarms {
		alarms[i] = Alarm{
			AreaNumber:  int16(data[i*5+2]) + 1,                       // 区域号+1
			Position:    float32(bit.ToUint16(data[i*5+3:i*5+5])) / 2, // 报警位置精度0.5M
			Temperature: float32(bit.ToUint16(data[i*5+5 : i*5+7])),   // 报警温度精度0.1℃ //todo
		}
		content += fmt.Sprintf("<%s>", alarms[i].String())
	}
	temperatureAlarm := ChannelTemperatureAlarm{Info: info, Alarms: alarms, At: time.Now()}
	switch dataFrame {
	case DataFrameConstantTemperatureAlarmStart:
		temperatureAlarm.Type = ConstantTemperature
		LoadCallsFilterCodeRun(CodeConstantTemperatureAlarm, temperatureAlarm)
		PushLog(W, fmt.Sprintf("测温主机<%s>设备码<%s>设备通道<%d>产生<%d>个定温报警: %s", info.Address, p.DeviceCode, channelNumber, len(temperatureAlarm.Alarms), content))
	case DataFrameDifferentialTemperatureAlarmStart:
		temperatureAlarm.Type = DifferentialTemperature
		LoadCallsFilterCodeRun(CodeDifferentialTemperatureAlarm, temperatureAlarm)
		PushLog(W, fmt.Sprintf("测温主机<%s>设备码<%s>通道<%d>产生<%d>个温差报警: %s", info.Address, p.DeviceCode, channelNumber, len(temperatureAlarm.Alarms), content))
	default:
		return
	}
	LoadCallsFilterCodeRun(CodeFiberAlarm, temperatureAlarm)
}

// 解码获取公共参数
func (p *Protocol) decodeCommonParams(data []byte) {
	LoadCallsFilterCodeRun(CodeQueryCM, CommonParams{
		ChannelFlag: bit.ToInt16(data[0:2]),
		EDFACurrent: bit.ToInt16(data[2:4]),
		IP:          fmt.Sprintf("%d.%d.%d.%d", data[4], data[5], data[6], data[7]),
		Port:        bit.ToInt16(data[8:10]),
	})
}

// 解码温度信息
func (p *Protocol) decodeTemperature(data []byte) {
	flag := DataFlag(data[0])
	_ = data[1:2]                    // 1~2 设备id
	_ = bit.ToInt16(data[2:4])       // 2~3 通道标志位
	start := bit.ToUint16(data[4:6]) // 4~5 数据起点位置
	end := bit.ToUint16(data[6:8])   // 6~7 数据终点位置
	count := bit.ToInt16(data[8:10]) // 8~9 总通道数
	channelNumber := data[10] + 1    // 10 当前通道号,获取的通道号从0开始,显示+1

	var (
		length       = (len(data) - 2 - 2 - 2 - 2 - 2 - 1 - 1) / 2
		temperatures = make([]float32, length)
		info         = DeviceInfo{
			DeviceCode:    p.DeviceCode,
			DeviceNumber:  p.DeviceNumber,
			ChannelNumber: channelNumber,
			Address:       p.client.conn.RemoteAddr().String(),
		}
	)

	for i := range temperatures {
		temperatures[i] = float32(bit.ToInt16(data[11+i*2:i*2+13])-20000) / float32(100)
	}
	channelTemperature := ChannelTemperature{
		Info:         info,
		Flag:         flag,
		ChannelCount: byte(count),
		Start:        float32(start) * p.Config.Precision,
		End:          float32(end) * p.Config.Precision,
		Temperatures: temperatures,
		At:           time.Now(),
	}
	LoadCallsFilterCodeRun(CodeTemperature, channelTemperature)
	PushLog(W, fmt.Sprintf("测温主机<%s>设备码<%s>通道<%d>在<%.1f~%.1f>米温度更新", info.Address, p.DeviceCode, channelNumber, channelTemperature.Start, channelTemperature.End))
}

// 解码获取通道计算参数
func (p *Protocol) decodeChannelCalculationParams(data []byte) {
	pulsePeriod := uint16(float32(bit.ToUint16(data[4:6]))*p.Config.Precision) / LightSpeed
	addTimes := bit.ToInt(data[6:10])
	LoadCallsFilterCodeRun(CodeQueryCA, ChannelCalculationParams{
		FiberStart:  float32(bit.ToUint16(data[0:2])) * p.Config.Precision,
		FiberEnd:    float32(bit.ToUint16(data[2:4])) * p.Config.Precision,
		FiberLength: float32(pulsePeriod * LightSpeed),
		MeasureTime: float32(math.Ceil(float64(int(pulsePeriod) * addTimes / Second))),
		PulsePeriod: pulsePeriod,
		AddTimes:    addTimes,
		PulseWidth:  uint16(float32(bit.ToUint16(data[10:12])) * p.Config.Precision),
		LaserPower:  bit.ToUint16(data[12:14]),
	})
}

// 解码获取设备编码
func (p *Protocol) decodeDeviceCode(data []byte) {
	p.DeviceCode = fmt.Sprintf("%d%02d%02d%02d%04d", (data[3]<<4)+((data[2]&0xF0)>>4), data[2]&0xF, data[1], (data[0]&0xF0)>>4, data[0]&0xF)
	if p.client.server != nil {
		p.client.setStatus(Connected)
		p.client.server.storeClient(p.client)
	}
}
